/*
 * Copyright (C) 2015-2019 Alibaba Group Holding Limited
 */

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include "aos/hal/sd.h"
#include "aos/kernel.h"
#include "aos/errno.h"

#include "fsl_sd.h"

#ifdef AOS_COMP_FATFS
static sd_card_t  st_sdcard_inst;
static aos_sem_t  sd_detect_sem;
static bool       sd_init_flag = false;
static bool       sd_card_inserted = false;
static bool       sd_card_init_ok  = false;
static aos_mutex_t sd_mux;


#define CARD_DETECT_TASK_STACK   2048
#define CARD_DETECT_TASK_PRIO    12
#define SD_DETECT_TASK_NAME      "sdcarddetect"
static void SDCARD_DetectCallBack(bool isInserted, void *userData);

/*! @brief Data block count accessed in card */
#define DATA_BLOCK_COUNT (1U)
/*! @brief Data buffer size. */
#define DATA_BUFFER_SIZE (FSL_SDMMC_DEFAULT_BLOCK_SIZE * DATA_BLOCK_COUNT)


SDK_ALIGN(uint8_t g_dataWrite[SDK_SIZEALIGN(DATA_BUFFER_SIZE, SDMMC_DATA_BUFFER_ALIGN_CACHE)],
          MAX(SDMMC_DATA_BUFFER_ALIGN_CACHE, SDMMCHOST_DMA_BUFFER_ADDR_ALIGN));
/*! @brief Data read from the card */
SDK_ALIGN(uint8_t g_dataRead[SDK_SIZEALIGN(DATA_BUFFER_SIZE, SDMMC_DATA_BUFFER_ALIGN_CACHE)],
          MAX(SDMMC_DATA_BUFFER_ALIGN_CACHE, SDMMCHOST_DMA_BUFFER_ADDR_ALIGN));


/*only support one sd card?*/
static const sdmmchost_detect_card_t s_sdCardDetect = {
#ifndef BOARD_SD_DETECT_TYPE
    .cdType = kSDMMCHOST_DetectCardByGpioCD,
#else
    .cdType = BOARD_SD_DETECT_TYPE,
#endif
    .cdTimeOut_ms = (~0U),
    .cardInserted = SDCARD_DetectCallBack,
    .cardRemoved = SDCARD_DetectCallBack,
};

static void SDCARD_DetectCallBack(bool isInserted, void *userData)
{
    if (sd_card_inserted == isInserted) {
        return;
    }
    sd_card_inserted = isInserted;
    aos_sem_signal(&sd_detect_sem);
}

static void CardDetectTask(void *pvParameters)
{
    int32_t ret = 0;
    
    while(true) {
        if (sd_card_inserted != true) {
            /* power off card */
            SD_PowerOffCard(st_sdcard_inst.host.base , st_sdcard_inst.usrParam.pwr);
        }

        aos_sem_wait(&sd_detect_sem, AOS_WAIT_FOREVER);
		aos_msleep(50);
        if (sd_card_inserted == true) {
            /*card inserted*/
            printf("sd card inserted \r\n");
            /*power on the card */
            SD_PowerOnCard(st_sdcard_inst.host.base , st_sdcard_inst.usrParam.pwr);
            /*init card*/
            ret = SD_CardInit(&st_sdcard_inst);
            if (ret != 0) {
                printf("SD_CardInit card init fail \r\n");
            } else {
                sd_card_init_ok = true;
                /*fatfs mount*/
                ret = fatfs_register();
                if (ret != 0) {
                    printf("SD Card fatfs mount fail \r\n");
                } else {
                    printf("sd card mount at /sdcard: \r\n");
                }
            }
        } else {
            sd_card_init_ok = false;
            fatfs_unregister();
            printf("removed sd card\r\n");
        }
    }
}


/**@brief Initialises a sd interface
*
* @param  sd       : the interface which should be initialised
* @param  config   : sd configuration structure
*
* @return    0     : on success.
* @return    EIO   : if an error occurred with any step
*/
int32_t hal_sd_init(sd_dev_t *sd)
{
    aos_task_t  task;

    if (NULL == sd) {
        printf("sd init fail for invalid input \r\n");
        return -1;
    }

    if (sd_init_flag == true){
        if (st_sdcard_inst.isHostReady == true) {
            SD_HostReset(&st_sdcard_inst);
            SD_PowerOffCard(st_sdcard_inst.host.base, st_sdcard_inst.usrParam.pwr);
        }
        return 0;
    }
    if(aos_mutex_new(&sd_mux)!=0){
        printf("\r\nSD mux init fail~\r\n");
        return -1;
    }
    
    sd_init_flag = true;
    /*SD CARD param should be init before card detect creat*/
    SD_Param_init(&st_sdcard_inst);
    st_sdcard_inst.usrParam.cd = &s_sdCardDetect;
    if (aos_sem_new(&sd_detect_sem, 0) != 0){
        printf("\r\n new SD card detect sem4 fail\r\n");
        return -1;
    }

    if (aos_task_new_ext(&task, SD_DETECT_TASK_NAME, CardDetectTask, NULL, CARD_DETECT_TASK_STACK, CARD_DETECT_TASK_PRIO) != 0 ) {
        printf("\r\n new sdcard detect task fail \r\n");
        aos_sem_free(&sd_detect_sem);
        return -1;
    }
    
    /* SD host init function */
    if (SD_HostInit(&st_sdcard_inst) != kStatus_Success) {
        aos_sem_free(&sd_detect_sem);
        aos_task_delete(SD_DETECT_TASK_NAME);
        printf("\r\nSD host init fail\r\n");
        return -1;
    }

    SD_PowerOffCard(st_sdcard_inst.host.base, st_sdcard_inst.usrParam.pwr);

    

    return 0;
}

/**@brief read sd blocks
*
* @param  sd       : the interface which should be initialised
* @param  data     : pointer to the buffer which will store incoming data
* @param  blk_addr : sd blk addr
* @param  blks     : sd blks
* @param  timeout  : timeout in milisecond
* @return    0     : on success.
* @return    EIO   : if an error occurred with any step
*/
int32_t hal_sd_blks_read(sd_dev_t *sd, uint8_t *data, uint32_t blk_addr, uint32_t blks, uint32_t timeout)
{
    status_t ret = 0;
    uint32_t blockLeft;
    uint32_t blockDone = 0;
    uint32_t blockCountOneTime = blks; 
    uint8_t *nextBuffer = data;
    
    if (NULL == sd || NULL == data) {
        printf("invalid input %s \r\n", __FUNCTION__);
        return -1;
    }

    if (sd_init_flag != true) {
        printf("%s sd  haven't init yet \r\n", __FUNCTION__);
        return -2;
    }
    
    if (sd_card_init_ok != true) {
         printf("%s there is no sd card \r\n", __FUNCTION__);
         return -3;
    }
    if(aos_mutex_lock(&sd_mux,50)!=0){
        return -5;
    }
    blockLeft = blks;
    blockCountOneTime = MIN(DATA_BLOCK_COUNT, blks);
    
    while (blockLeft) {
        if (blockCountOneTime > blockLeft) {
            blockCountOneTime = blockLeft;
        }
        
        ret = SD_ReadBlocks(&st_sdcard_inst, g_dataRead, blk_addr, blockCountOneTime);
        if (ret != kStatus_Success) {
            printf("SD_ReadBlocks fail ret %d start block , blocks %d \r\n", 
                    ret, blk_addr, blockCountOneTime);
            aos_mutex_unlock(&sd_mux);
            return -4;
        }
        
        memcpy(nextBuffer, (uint8_t *)g_dataRead, blockCountOneTime * FSL_SDMMC_DEFAULT_BLOCK_SIZE);
        blockDone += blockCountOneTime;
        blockLeft -= blockCountOneTime;
        blk_addr += blockCountOneTime;
        nextBuffer = (data + blockDone * FSL_SDMMC_DEFAULT_BLOCK_SIZE);
    }
    aos_mutex_unlock(&sd_mux);
    return 0;
}


/**@brief write sd blocks
*
* @param  sd       : the interface which should be initialised
* @param  data     : pointer to the buffer which will store incoming data
* @param  blk_addr : sd blk addr
* @param  blks     : sd blks
* @param  timeout  : timeout in milisecond
* @return    0     : on success.
* @return    EIO   : if an error occurred with any step
*/
int32_t hal_sd_blks_write(sd_dev_t *sd, uint8_t *data, uint32_t blk_addr, uint32_t blks, uint32_t timeout)
{
    status_t ret = 0;
    uint32_t blockLeft;
    uint32_t blockDone = 0;
    uint32_t blockCountOneTime = blks; 
    uint8_t *nextBuffer = data;
    
    if (NULL == sd || NULL == data) {
        printf("invalid input %s \r\n", __FUNCTION__);
        return -1;
    }

    if (sd_init_flag != true) {
        printf("%s sd haven't init yet \r\n", __FUNCTION__);
        return -2;
    }
    
    if (sd_card_init_ok != true) {
         printf("%s there is no sd card \r\n", __FUNCTION__);
         return -3;
    }

    if(SD_CheckReadOnly(&st_sdcard_inst) == true) {
        printf("sd card is read only");
        return -4;
    }
    if(aos_mutex_lock(&sd_mux,50)!=0){
        return -5;
    }
    blockLeft = blks;
    blockCountOneTime = MIN(DATA_BLOCK_COUNT, blks);
    
    while (blockLeft) {
        if (blockCountOneTime > blockLeft) {
            blockCountOneTime = blockLeft;
        }
        
        memcpy(g_dataWrite, nextBuffer, blockCountOneTime * FSL_SDMMC_DEFAULT_BLOCK_SIZE);
        ret = SD_WriteBlocks(&st_sdcard_inst, g_dataWrite, blk_addr, blockCountOneTime);
        if (ret != kStatus_Success) {
            printf("SD_WriteBlocks fail ret %d start block %d, blocks %d \r\n", 
                    ret, blk_addr, blockCountOneTime);
            aos_mutex_unlock(&sd_mux);
            return -4;
        }
        
        blockDone += blockCountOneTime;
        blockLeft -= blockCountOneTime;
        nextBuffer = (data + blockDone * FSL_SDMMC_DEFAULT_BLOCK_SIZE);
    }
    aos_mutex_unlock(&sd_mux);
    return 0;
}

/**@brief erase sd blocks
*
* @param  sd              : the interface which should be initialised
* @param  blk_start_addr  : sd blocks start addr
* @param  blk_end_addr    : sd blocks end addr
* @return    0            : on success.
* @return    EIO          : if an error occurred with any step
*/
int32_t hal_sd_erase(sd_dev_t *sd, uint32_t blk_start_addr, uint32_t blk_end_addr)
{
    status_t ret = 0;
    
    if (NULL == sd) {
        printf("invalid input to erase sd card \r\n");
        return -1;
    }
    
    if (sd_init_flag != true) {
        printf("fail to erase sd card, sd haven't init yet \r\n");
        return -2;
    }
    
    if (sd_card_init_ok != true) {
         printf("fail to erase sd card, there is no sd card \r\n");
         return -3;
    }
    if(aos_mutex_lock(&sd_mux,2000)!=0){
        return -6;
    }
    ret = SD_EraseBlocks(&st_sdcard_inst, blk_start_addr, 
                        blk_end_addr - blk_start_addr + 1);
    if (ret != kStatus_Success) {
        printf("SD_EraseBlocks fail ret %d start block %d, end block %d\r\n", 
                ret, blk_start_addr, blk_end_addr);
        aos_mutex_unlock(&sd_mux);
        return -5;
    }
    aos_mutex_unlock(&sd_mux);
    return 0;
}

/**@brief get sd state
*
* @param  sd       : the interface which should be initialised
* @param  stat     : pointer to the buffer which will store incoming data
* @return    0     : on success.
* @return    EIO   : if an error occurred with any step
*/
int32_t hal_sd_stat_get(sd_dev_t *sd, hal_sd_stat *stat)
{
    if (NULL == sd || NULL == stat) {
        printf("invalid input to get sd state \r\n");
        return -1;
    }

    if (sd_init_flag != true) {
        printf("fail to get sd state, sd haven't init yet \r\n");
        return -2;
    }

    if (sd_card_inserted != true) {
         printf("fail to get sd state, there is no sd card \r\n");
         return -3;
    }

    if (sd_card_init_ok == true) {
        *stat = SD_STAT_TRANSFER;
    } else {
        *stat = SD_STAT_ERR;
    }
    
    return 0;
}

/**@brief get sd info
*
* @param  sd       : the interface which should be initialised
* @param  stat     : pointer to the buffer which will store incoming data
* @return    0     : on success.
* @return    EIO   : if an error occurred with any step
*/
int32_t hal_sd_info_get(sd_dev_t *sd, hal_sd_info_t *info)
{
    if (NULL == info || NULL == sd) {
        printf("invalid input to get sd info \r\n");
        return -1;
    }

    if (sd_init_flag != true) {
        printf("fail to get sd info, sd haven't init yet \r\n");
        return -2;
    }
 
    if (sd_card_init_ok != true) {
         printf("fail to get sd info, there is no sd card \r\n");
         return -3;
    }
    
    info->blk_nums = st_sdcard_inst.blockCount;
    info->blk_size = st_sdcard_inst.blockSize;

    return 0;
}

/**@brief Deinitialises a sd interface
*
* @param  sd       : the interface which should be initialised
* @return    0     : on success.
* @return    EIO   : if an error occurred with any step
*/
int32_t hal_sd_finalize(sd_dev_t *sd)
{
    if (sd_init_flag != true) {
        return 0;
    }

    SD_Deinit(&st_sdcard_inst);
    aos_task_delete(SD_DETECT_TASK_NAME);
    aos_sem_free(&sd_detect_sem);
    
    sd_card_inserted = false;
    sd_init_flag = false;
    sd_card_init_ok = false;
    
    return 0;
}
#endif

